#include <algorithm>
#include <climits>
#include <cstddef>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <list>
#include <string>

#include "Map.h"

using namespace std;

Map::Map()
{
  m_board = NULL;
}

Map::~Map()
{
  if (m_board != NULL) {
    for (unsigned int y = 0; y < m_boardY; ++y) {
      for (unsigned int x = 0; x< m_boardX; ++x) {
	delete m_board[y][x];
      }
      delete[] m_board[y];
    }
    delete[] m_board;
  }
}

bool Map::LoadMap(const char* mapFilename)
{
  bool retValue = false;
  ifstream mapFile;
  string line;
  int lineCount = 0;
  int lineWidth = 0;

  // First figure out the dimensions of the map
  mapFile.open(mapFilename, ios::in);
  if (mapFile.is_open()) {
    while (!mapFile.eof()) {
      getline(mapFile, line);
      if (!mapFile.eof()) {
	lineCount++;

	if (lineWidth == 0) { // Only update once
	  lineWidth = line.length();
	}
      }
    }

    mapFile.close();
  } else {
    return retValue;
  }

  m_boardY = lineCount;
  m_boardX = lineWidth;

  m_board = new Node**[m_boardY];

  // Now read the map into memory
  mapFile.open(mapFilename, ios::in);
  if (mapFile.is_open()) {
    for (unsigned int y = 0; y < m_boardY; ++y) {
      getline(mapFile, line);

      m_board[y] = new Node*[m_boardX];

      for (unsigned int x = 0; x < m_boardX; ++x) {
	m_board[y][x] = new Node(x, y, line[x]);
	if (line[x] == 'S') {
	  m_start.SetXY(x, y);
	} else if (line[x] == 'E') {
	  m_end.SetXY(x, y);
	}
      }
    }
    mapFile.close();
    retValue = true;
  } else {
    return retValue;
  }

  return retValue;
}

void Map::PrettyPrint()
{
  if (m_board == NULL) {
    return;
  }

  for (unsigned int y = 0; y < m_boardY; ++y) {
    for (unsigned int x = 0; x < m_boardX; ++x) {
      cout << m_board[y][x]->GetData() << " ";
    }
    cout << endl;
  }
}

void Map::PrintPath()
{
  Node* curNode = GetBoardNode(GetEndPoint());

  //cout << *curNode << endl;

  // Note '!=' operator is not yet defined for Point objects
  while (!(curNode->GetCoords() == GetStartPoint())) {
    curNode = curNode->GetParent();
    //cout << *curNode << endl;
    if (!(curNode->GetCoords() == GetStartPoint())) {
      curNode->SetData('O');
    }
  }

  PrettyPrint();
}

void Map::PrintDijkstras()
{
  Node* curNode;
  for (unsigned int y = 0; y < m_boardY; ++y) {
    for (unsigned int x = 0; x < m_boardX; ++x) {
      curNode = m_board[y][x];
      cout << curNode->GetData();
      if (IsWalkable(curNode->GetCoords()) &&
	  curNode->GetF() != INT_MAX) {
	cout << "(" << setw(3) << curNode->GetF() << ") ";
      } else {
	cout << "(   ) ";
      }
    }
    cout << endl;
  }
}

void Map::PrintDigraph()
{
  Node* curNode;
  list<Point> pid; // List of points in the digraph. Duplicates
                   // would mean excessive lines shown by graph-vis
  int f, pf;

  cout << "digraph Map {" << endl;

  for (unsigned int y = 0; y < m_boardY; ++y) {
    for (unsigned int x = 0; x < m_boardX; ++x) {
      curNode = m_board[y][x];

      if (!IsWalkable(curNode->GetCoords()) ||
	  curNode->GetParent() == NULL ||
	  curNode->GetData() == 'S') {
	continue;
      }

      cout << "\t";

      f = curNode->GetF();
      pf = curNode->GetParent()->GetF();

      cout << "\"" << curNode->GetParent()->GetCoords()
	   << "\" -> \"" << curNode->GetCoords()
	   << "\" [label=\"" << (f - pf) << " (" << f
	   << ")\"];" << endl;
    }
  }

  cout << "}" << endl;
}

bool Map::IsWalkable(Point p)
{
  return IsWalkable(p.GetX(), p.GetY());
}

bool Map::IsWalkable(int x, int y)
{
  if (x < 0 || y < 0 ||
      (unsigned int)x >= m_boardX || (unsigned int)y >= m_boardY) {
    return false;
  }

  char data = m_board[y][x]->GetData();

  if (data == 'W')
    return false;
  return true;
}

Node* Map::GetBoardNode(Point p)
{
  return GetBoardNode(p.GetX(), p.GetY());
}

Node* Map::GetBoardNode(int x, int y)
{
  if (x < 0 || y < 0 ||
      (unsigned int)x >= m_boardX || (unsigned int)y >= m_boardY)
    return NULL;
  return m_board[y][x];
}
